// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/filters/source_buffer_state.h"

#include <vector>

#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/mock_media_log.h"
#include "media/base/test_helpers.h"
#include "media/filters/frame_processor.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

using testing::_;
using testing::SaveArg;

namespace {

    AudioDecoderConfig CreateAudioConfig(AudioCodec codec)
    {
        return AudioDecoderConfig(codec, kSampleFormatPlanarF32,
            CHANNEL_LAYOUT_STEREO, 1000, EmptyExtraData(),
            Unencrypted());
    }

    VideoDecoderConfig CreateVideoConfig(VideoCodec codec, int w, int h)
    {
        gfx::Size size(w, h);
        gfx::Rect visible_rect(size);
        return VideoDecoderConfig(codec, VIDEO_CODEC_PROFILE_UNKNOWN,
            PIXEL_FORMAT_YV12, COLOR_SPACE_HD_REC709, size,
            visible_rect, size, EmptyExtraData(),
            Unencrypted());
    }

    void AddAudioTrack(std::unique_ptr<MediaTracks>& t, AudioCodec codec, int id)
    {
        t->AddAudioTrack(CreateAudioConfig(codec), id, "", "", "");
    }

    void AddVideoTrack(std::unique_ptr<MediaTracks>& t, VideoCodec codec, int id)
    {
        t->AddVideoTrack(CreateVideoConfig(codec, 16, 16), id, "", "", "");
    }

    void InvokeCbAndSaveResult(const base::Callback<bool()>& cb, bool* result)
    {
        DCHECK(result);
        *result = cb.Run();
    }
}

class SourceBufferStateTest : public ::testing::Test {
public:
    SourceBufferStateTest()
        : media_log_(new testing::StrictMock<MockMediaLog>())
        , mock_stream_parser_(nullptr)
    {
    }

    std::unique_ptr<SourceBufferState> CreateSourceBufferState()
    {
        std::unique_ptr<FrameProcessor> frame_processor = base::WrapUnique(
            new FrameProcessor(base::Bind(&SourceBufferStateTest::OnUpdateDuration,
                                   base::Unretained(this)),
                media_log_));
        mock_stream_parser_ = new testing::StrictMock<MockStreamParser>();
        return base::WrapUnique(new SourceBufferState(
            base::WrapUnique(mock_stream_parser_), std::move(frame_processor),
            base::Bind(&SourceBufferStateTest::CreateDemuxerStream,
                base::Unretained(this)),
            media_log_));
    }

    std::unique_ptr<SourceBufferState> CreateAndInitSourceBufferState(
        const std::string& expected_codecs)
    {
        std::unique_ptr<SourceBufferState> sbs = CreateSourceBufferState();
        EXPECT_CALL(*mock_stream_parser_, Init(_, _, _, _, _, _, _, _))
            .WillOnce(SaveArg<1>(&new_config_cb_));
        sbs->Init(base::Bind(&SourceBufferStateTest::SourceInitDone,
                      base::Unretained(this)),
            expected_codecs,
            base::Bind(&SourceBufferStateTest::StreamParserEncryptedInitData,
                base::Unretained(this)),
            base::Bind(&SourceBufferStateTest::StreamParserNewTextTrack,
                base::Unretained(this)));

        sbs->SetTracksWatcher(base::Bind(
            &SourceBufferStateTest::OnMediaTracksUpdated, base::Unretained(this)));
        return sbs;
    }

    // Emulates appending some data to the SourceBufferState, since OnNewConfigs
    // can only be invoked when append is in progress.
    bool AppendDataAndReportTracks(const std::unique_ptr<SourceBufferState>& sbs,
        std::unique_ptr<MediaTracks> tracks)
    {
        const uint8_t stream_data[] = "stream_data";
        const int data_size = sizeof(stream_data);
        base::TimeDelta t;
        StreamParser::TextTrackConfigMap text_track_config_map;

        bool new_configs_result = false;
        base::Closure new_configs_closure = base::Bind(InvokeCbAndSaveResult,
            base::Bind(new_config_cb_, base::Passed(std::move(tracks)),
                text_track_config_map),
            &new_configs_result);
        EXPECT_CALL(*mock_stream_parser_, Parse(stream_data, data_size))
            .WillOnce(testing::DoAll(RunClosure(new_configs_closure),
                testing::Return(true)));
        sbs->Append(stream_data, data_size, t, t, &t);
        return new_configs_result;
    }

    MOCK_METHOD1(OnUpdateDuration, void(base::TimeDelta));

    MOCK_METHOD1(SourceInitDone, void(const StreamParser::InitParameters&));
    MOCK_METHOD2(StreamParserEncryptedInitData,
        void(EmeInitDataType, const std::vector<uint8_t>&));
    MOCK_METHOD2(StreamParserNewTextTrack,
        void(ChunkDemuxerStream*, const TextTrackConfig&));

    MOCK_METHOD1(MediaTracksUpdatedMock, void(std::unique_ptr<MediaTracks>&));
    void OnMediaTracksUpdated(std::unique_ptr<MediaTracks> tracks)
    {
        MediaTracksUpdatedMock(tracks);
    }

    ChunkDemuxerStream* CreateDemuxerStream(DemuxerStream::Type type)
    {
        static unsigned track_id = 0;
        demuxer_streams_.push_back(base::WrapUnique(
            new ChunkDemuxerStream(type, base::UintToString(++track_id))));
        return demuxer_streams_.back().get();
    }

    scoped_refptr<testing::StrictMock<MockMediaLog>> media_log_;
    std::vector<std::unique_ptr<ChunkDemuxerStream>> demuxer_streams_;
    MockStreamParser* mock_stream_parser_;
    StreamParser::NewConfigCB new_config_cb_;
};

TEST_F(SourceBufferStateTest, InitSingleAudioTrack)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("vorbis");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddAudioTrack(tracks, kCodecVorbis, 1);

    EXPECT_MEDIA_LOG(FoundStream("audio"));
    EXPECT_MEDIA_LOG(CodecName("audio", "vorbis"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, InitSingleVideoTrack)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("vp8");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddVideoTrack(tracks, kCodecVP8, 1);

    EXPECT_MEDIA_LOG(FoundStream("video"));
    EXPECT_MEDIA_LOG(CodecName("video", "vp8"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, InitMultipleTracks)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("vorbis,vp8,opus,vp9");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddAudioTrack(tracks, kCodecVorbis, 1);
    AddAudioTrack(tracks, kCodecOpus, 2);
    AddVideoTrack(tracks, kCodecVP8, 3);
    AddVideoTrack(tracks, kCodecVP9, 4);

    EXPECT_MEDIA_LOG(FoundStream("audio")).Times(2);
    EXPECT_MEDIA_LOG(CodecName("audio", "vorbis"));
    EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
    EXPECT_MEDIA_LOG(FoundStream("video")).Times(2);
    EXPECT_MEDIA_LOG(CodecName("video", "vp8"));
    EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, AudioStreamMismatchesExpectedCodecs)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("opus");
    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddAudioTrack(tracks, kCodecVorbis, 1);
    EXPECT_MEDIA_LOG(InitSegmentMismatchesMimeType("Audio", "vorbis"));
    EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, VideoStreamMismatchesExpectedCodecs)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("vp9");
    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddVideoTrack(tracks, kCodecVP8, 1);
    EXPECT_MEDIA_LOG(InitSegmentMismatchesMimeType("Video", "vp8"));
    EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, MissingExpectedAudioStream)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("opus,vp9");
    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddVideoTrack(tracks, kCodecVP9, 1);
    EXPECT_MEDIA_LOG(FoundStream("video"));
    EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
    EXPECT_MEDIA_LOG(InitSegmentMissesExpectedTrack("opus"));
    EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, MissingExpectedVideoStream)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("opus,vp9");
    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    tracks->AddAudioTrack(CreateAudioConfig(kCodecOpus), 1, "", "", "");
    EXPECT_MEDIA_LOG(FoundStream("audio"));
    EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
    EXPECT_MEDIA_LOG(InitSegmentMissesExpectedTrack("vp9"));
    EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}

TEST_F(SourceBufferStateTest, TrackIdsChangeInSecondInitSegment)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("opus,vp9");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddAudioTrack(tracks, kCodecOpus, 1);
    AddVideoTrack(tracks, kCodecVP9, 2);
    EXPECT_MEDIA_LOG(FoundStream("audio"));
    EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
    EXPECT_MEDIA_LOG(FoundStream("video"));
    EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    AppendDataAndReportTracks(sbs, std::move(tracks));

    // This second set of tracks have bytestream track ids that differ from the
    // first init segment above (audio track id 1 -> 3, video track id 2 -> 4).
    // Bytestream track ids are allowed to change when there is only a single
    // track of each type.
    std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
    AddAudioTrack(tracks2, kCodecOpus, 3);
    AddVideoTrack(tracks2, kCodecVP9, 4);
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    AppendDataAndReportTracks(sbs, std::move(tracks2));
}

TEST_F(SourceBufferStateTest, TrackIdChangeWithTwoAudioTracks)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("vorbis,opus");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddAudioTrack(tracks, kCodecVorbis, 1);
    AddAudioTrack(tracks, kCodecOpus, 2);
    EXPECT_MEDIA_LOG(FoundStream("audio")).Times(2);
    EXPECT_MEDIA_LOG(CodecName("audio", "vorbis"));
    EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));

    // Since we have two audio tracks, bytestream track ids must match the first
    // init segment.
    std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
    AddAudioTrack(tracks2, kCodecVorbis, 1);
    AddAudioTrack(tracks2, kCodecOpus, 2);
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks2)));

    // Emulate the situation where bytestream track ids have changed in the third
    // init segment. This must cause failure in the OnNewConfigs.
    std::unique_ptr<MediaTracks> tracks3(new MediaTracks());
    AddAudioTrack(tracks3, kCodecVorbis, 1);
    AddAudioTrack(tracks3, kCodecOpus, 3);
    EXPECT_MEDIA_LOG(UnexpectedTrack("audio", "3"));
    EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks3)));
}

TEST_F(SourceBufferStateTest, TrackIdChangeWithTwoVideoTracks)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("vp8,vp9");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddVideoTrack(tracks, kCodecVP8, 1);
    AddVideoTrack(tracks, kCodecVP9, 2);
    EXPECT_MEDIA_LOG(FoundStream("video")).Times(2);
    EXPECT_MEDIA_LOG(CodecName("video", "vp8"));
    EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));

    // Since we have two video tracks, bytestream track ids must match the first
    // init segment.
    std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
    AddVideoTrack(tracks2, kCodecVP8, 1);
    AddVideoTrack(tracks2, kCodecVP9, 2);
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks2)));

    // Emulate the situation where bytestream track ids have changed in the third
    // init segment. This must cause failure in the OnNewConfigs.
    std::unique_ptr<MediaTracks> tracks3(new MediaTracks());
    AddVideoTrack(tracks3, kCodecVP8, 1);
    AddVideoTrack(tracks3, kCodecVP9, 3);
    EXPECT_MEDIA_LOG(UnexpectedTrack("video", "3"));
    EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks3)));
}

TEST_F(SourceBufferStateTest, TrackIdsSwappedInSecondInitSegment)
{
    std::unique_ptr<SourceBufferState> sbs = CreateAndInitSourceBufferState("opus,vp9");

    std::unique_ptr<MediaTracks> tracks(new MediaTracks());
    AddAudioTrack(tracks, kCodecOpus, 1);
    AddVideoTrack(tracks, kCodecVP9, 2);
    EXPECT_MEDIA_LOG(FoundStream("audio"));
    EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
    EXPECT_MEDIA_LOG(FoundStream("video"));
    EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    AppendDataAndReportTracks(sbs, std::move(tracks));

    // Track ids are swapped in the second init segment.
    std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
    AddAudioTrack(tracks2, kCodecOpus, 2);
    AddVideoTrack(tracks2, kCodecVP9, 1);
    EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
    AppendDataAndReportTracks(sbs, std::move(tracks2));
}

} // namespace media
